package cn.iocoder.yudao.module.bk.service.lwbook;

import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bk.controller.admin.lwbook.vo.*;
import cn.iocoder.yudao.module.bk.controller.admin.lwbookinapply.vo.LwBookInApplyRes;
import cn.iocoder.yudao.module.bk.controller.admin.lwbookoutapply.vo.LwBookOutApplyRes;
import cn.iocoder.yudao.module.bk.controller.app.vo.*;
import cn.iocoder.yudao.module.bk.dal.dataobject.lwbook.LwBookDO;
import cn.iocoder.yudao.module.bk.dal.dataobject.lwbookinapply.LwBookInApplyDO;
import cn.iocoder.yudao.module.bk.dal.dataobject.lwbookoutapply.LwBookOutApplyDO;
import cn.iocoder.yudao.module.bk.dal.dataobject.publisher.PublisherDO;
import cn.iocoder.yudao.module.bk.dal.enm.BookStoreEnum;
import cn.iocoder.yudao.module.bk.dal.enm.LwProcessEnum;
import cn.iocoder.yudao.module.bk.dal.mysql.lwbook.LwBookMapper;
import cn.iocoder.yudao.module.bk.dal.mysql.lwbookinapply.LwBookInApplyMapper;
import cn.iocoder.yudao.module.bk.dal.mysql.lwbookoutapply.LwBookOutApplyMapper;
import cn.iocoder.yudao.module.bk.dal.mysql.publisher.PublisherMapper;
import cn.iocoder.yudao.module.bk.util.ISBNUtils;
import cn.iocoder.yudao.module.member.api.user.MemberUserApi;
import cn.iocoder.yudao.module.member.api.user.dto.MemberUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.bk.enums.ErrorCodeConstants.LW_BOOK_NOT_EXISTS;

/**
 * 来往书籍 Service 实现类
 *
 * @author qy
 */
@Service
@Validated
public class LwBookServiceImpl implements LwBookService {

    @Resource
    private LwBookMapper lwBookMapper;
    @Resource
    private LwBookInApplyMapper bookInApplyMapper;
    @Resource
    private LwBookOutApplyMapper bookOutApplyMapper;
    @Resource
    private MemberUserApi memberUserApi;
    @Resource
    private PublisherMapper publisherMapper;

    @Override
    public Long createLwBook(LwBookSaveReqVO createReqVO) {
        // 插入
        LwBookDO lwBook = BeanUtils.toBean(createReqVO, LwBookDO.class);
        // 检查是否有黑名单出版社
        if (StrUtil.isNotEmpty(lwBook.getPublisher())) {
            Long blackListCount = publisherMapper.selectCount(new LambdaQueryWrapperX<PublisherDO>()
                    .eq(PublisherDO::getStatus, 1)
                    .eq(PublisherDO::getType, 1)
                    .eq(PublisherDO::getName, lwBook.getPublisher()));
            if (blackListCount > 0) {
                throw new ServiceException("黑名单出版社,无法添加");
            }
        }
        if (lwBook.getDonor() == null) {
            lwBook.setDonor(-1L);
        }
        lwBook.setStoreTime(LocalDateTime.now());
        lwBook.setApplyStatus(LwProcessEnum.PASS.getProcess());
        lwBookMapper.insert(lwBook);
        // 返回
        return lwBook.getId();
    }

    @Override
    public void updateLwBook(LwBookSaveReqVO updateReqVO) {
        // 校验存在
        validateLwBookExists(updateReqVO.getId());
        // 更新
        LwBookDO updateObj = BeanUtils.toBean(updateReqVO, LwBookDO.class);
        lwBookMapper.updateById(updateObj);
    }

    @Override
    public void deleteLwBook(Long id) {
        // 校验存在
        validateLwBookExists(id);
        // 删除
        lwBookMapper.deleteById(id);
    }

    private void validateLwBookExists(Long id) {
        if (lwBookMapper.selectById(id) == null) {
            throw exception(LW_BOOK_NOT_EXISTS);
        }
    }

    @Override
    public LwBookDO getLwBook(Long id) {
        return lwBookMapper.selectById(id);
    }

    @Override
    public PageResult<LwBookDO> getLwBookPage(LwBookPageReqVO pageReqVO) {
        PageResult<LwBookDO> res = lwBookMapper.selectPage(pageReqVO);
        Set<Long> ids = res.getList().stream().map(LwBookDO::getDonor).collect(Collectors.toSet());
        Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(ids);
        for (LwBookDO lwBookDO : res.getList()) {
            if (userMap.get(lwBookDO.getDonor()) == null) {
                continue;
            }
            lwBookDO.setDonorName(userMap.get(lwBookDO.getDonor()).getName());
        }
        Set<Long> hIds = res.getList().stream().map(LwBookDO::getNowHolder).collect(Collectors.toSet());
        Map<Long, MemberUserRespDTO> holderMap = memberUserApi.getUserMap(hIds);

        List<PublisherDO> publisherDOS = publisherMapper.selectList(new LambdaQueryWrapperX<PublisherDO>()
                .select(PublisherDO::getId, PublisherDO::getName)
                .eq(PublisherDO::getStatus, 1)
        );

        for (LwBookDO lwBookDO : res.getList()) {
            //是否是合规出版社
            boolean isCompliance = publisherDOS.stream()
                    .anyMatch(publisherDO -> publisherDO.getName().equals(lwBookDO.getPublisher()));
            lwBookDO.setCompliance(isCompliance);
            if (lwBookDO.getNowHolder() == null) {
                continue;
            }
            lwBookDO.setNowHolderName(holderMap.get(lwBookDO.getNowHolder()).getName());
        }
        return res;
    }

    @Override
    public PageResult<LwBookDO> getInLwBookPage(LwBookPageReqVO pageReqVO) {
        PageResult<LwBookDO> res = lwBookMapper.selectPage(pageReqVO, new LambdaQueryWrapperX<LwBookDO>()
                .eqIfPresent(LwBookDO::getStoreStatus, BookStoreEnum.IN_STORE.getProcess())
                .eqIfPresent(LwBookDO::getApplyStatus, LwProcessEnum.PASS.getProcess())
                .likeIfPresent(LwBookDO::getTitle, pageReqVO.getTitle())
                .likeIfPresent(LwBookDO::getIsbn, pageReqVO.getIsbn())
                .orderByDesc(LwBookDO::getStoreTime)
        );
        Set<Long> ids = res.getList().stream().map(LwBookDO::getDonor).collect(Collectors.toSet());
        Map<Long, MemberUserRespDTO> userMap = memberUserApi.getUserMap(ids);
        for (LwBookDO lwBookDO : res.getList()) {
            lwBookDO.setDonorName(userMap.get(lwBookDO.getDonor()).getName());
        }
        return res;
    }

    @Override
    public CommonResult<HomeInfoRes> nowInfo() {
        Long loginUserId = getLoginUserId();
        Long inNum = lwBookMapper.selectCount(new LambdaQueryWrapperX<LwBookDO>()
                .eq(LwBookDO::getDonor, loginUserId)
                .eq(LwBookDO::getApplyStatus, LwProcessEnum.PASS.getProcess()));
        Long outNum = lwBookMapper.selectCount(new LambdaQueryWrapperX<LwBookDO>()
                .eq(LwBookDO::getNowHolder, loginUserId));
        HomeInfoRes homeInfoRes = new HomeInfoRes();
        homeInfoRes.setInNum(inNum);
        homeInfoRes.setOutNum(outNum);
        homeInfoRes.setCanOut((inNum - outNum));
        return CommonResult.success(homeInfoRes);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<?> bookInApply(PutBookInReq req) {
        if (req.getBooks().isEmpty()) {
            throw new ServiceException("请上传需要漂入的书籍信息");
        }
        Long loginUserId = getLoginUserId();
        List<LwBookDO> books = new ArrayList<>();
        //获取当前用户正在申请中的数量
        Long count = bookInApplyMapper.selectCount(new LambdaQueryWrapperX<LwBookInApplyDO>()
                .eq(LwBookInApplyDO::getApplyUid, loginUserId)
                .and(wrapper -> wrapper
                        .eq(LwBookInApplyDO::getProcessStatus, LwProcessEnum.FIRST_EXAMINE.getProcess())
                        .or()
                        .eq(LwBookInApplyDO::getProcessStatus, LwProcessEnum.FINAL_EXAMINE.getProcess())
                )
        );
        if (count > 0) {
            throw new ServiceException("有申请未完成\n请等待审核");
        }
        // 检查是否有黑名单出版社
        Set<String> publishers = req.getBooks().stream()
                .map(BookInfoReq::getPublisher)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        if (!publishers.isEmpty()) {
            Long blackListCount = publisherMapper.selectCount(new LambdaQueryWrapperX<PublisherDO>()
                    .eq(PublisherDO::getStatus, 1)
                    .eq(PublisherDO::getType, 1)
                    .in(PublisherDO::getName, publishers));
            if (blackListCount > 0) {
                throw new ServiceException("黑名单出版社");
            }
        }
        //新增书籍
        for (BookInfoReq book : req.getBooks()) {
            books.add(new LwBookDO(book, loginUserId, LwProcessEnum.FIRST_EXAMINE.getProcess()));
        }
        lwBookMapper.insertBatch(books);
        //获取所有的id用逗号连接
        String bookIds = books.stream().map(LwBookDO::getId).map(String::valueOf).collect(Collectors.joining(","));
        LwBookInApplyDO apply = LwBookInApplyDO.builder().build()
                .setLwBookIds(bookIds)
                .setApplyUid(loginUserId)
                .setProcessStatus(LwProcessEnum.FIRST_EXAMINE.getProcess());
        bookInApplyMapper.insert(apply);
        //250720调整为初审即完成审核，无需终审，因此无需判断出版社
//        //判断所有书籍是否都是认证出版社
//        Set<String> collect = books.stream().map(LwBookDO::getPublisher).collect(Collectors.toSet());
//        Long pCount = publisherMapper.selectCount(new LambdaQueryWrapperX<PublisherDO>()
//                .eq(PublisherDO::getStatus, 1)
//                .eq(PublisherDO::getType, "0") // 0表示白名单出版社
//                .in(PublisherDO::getName, collect));
//        if (collect.size() == pCount) {
//            apply.setProcessStatus(LwProcessEnum.FINAL_EXAMINE.getProcess());
//            apply.setInitialReviewUid(141L);
//            apply.setInitialReviewDes("认证出版社");
//            bookInApplyMapper.updateById(apply);
//        }
        return CommonResult.success();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CommonResult<?> bookOutApply(PutBookOutReq req) {
        if (req.getBooks().isEmpty()) {
            throw new ServiceException("请上传需要漂出的书籍信息");
        }
        Long loginUserId = getLoginUserId();
        //获取当前用户正在申请中的数量
        Long count = bookOutApplyMapper.selectCount(new LambdaQueryWrapperX<LwBookOutApplyDO>()
                .eq(LwBookOutApplyDO::getApplyUid, loginUserId)
                .and(wrapper -> wrapper.eq(LwBookOutApplyDO::getProcessStatus, LwProcessEnum.FIRST_EXAMINE.getProcess())
                        .or()
                        .eq(LwBookOutApplyDO::getProcessStatus, LwProcessEnum.FINAL_EXAMINE.getProcess())
                )
        );
        if (count > 0) {
            throw new ServiceException("有申请未完成\n请耐心等待");
        }
        if (todayOutBookCount(loginUserId) >= 10) {
            throw new ServiceException("今日漂出十本\n明天再来吧");
        }
        //漂出漂入检测
        Long holdCount = lwBookMapper.selectCount(LwBookDO::getNowHolder, loginUserId);
        Long donorCount = lwBookMapper.selectCount(new LambdaQueryWrapperX<LwBookDO>()
                .eq(LwBookDO::getDonor, loginUserId)
                .eq(LwBookDO::getApplyStatus, LwProcessEnum.PASS.getProcess()));
        if (holdCount + req.getBooks().size() > donorCount) {
            throw new ServiceException("您目前最多可漂出" + (donorCount - holdCount) + "本书籍");
        }
        List<Long> bookIds = new ArrayList<>();
        //漂出的书籍
        for (BookInfoReq book : req.getBooks()) {
            LwBookDO lwBookDO = lwBookMapper.selectOne(new LambdaQueryWrapperX<LwBookDO>()
                    .eq(LwBookDO::getTitle, book.getTitle()).eq(LwBookDO::getIsbn, book.getIsbn())
                    .eq(LwBookDO::getStoreStatus, BookStoreEnum.IN_STORE.getProcess())
                    .orderByDesc(LwBookDO::getId)
                    .last("limit 1"));
            if (lwBookDO == null) {
                throw new ServiceException("未找到在库书籍:" + book.getTitle());
            }
            bookIds.add(lwBookDO.getId());
        }
        //申请中的书籍更新为已漂出,更新持有者
        lwBookMapper.update(new LambdaUpdateWrapper<LwBookDO>()
                .set(LwBookDO::getNowHolder, loginUserId)
                .set(LwBookDO::getStoreStatus, BookStoreEnum.OUT_STORE.getProcess())
                .in(LwBookDO::getId, bookIds));
        //提交漂出申请
        String bookIdsStr = bookIds.stream().map(String::valueOf).collect(Collectors.joining(","));
        bookOutApplyMapper.insert(new LwBookOutApplyDO(bookIdsStr, loginUserId));
        return CommonResult.success();
    }

    private Long todayOutBookCount(Long loginUserId) {
        //当天已提交不处于驳回状态的所有申请
        List<LwBookOutApplyDO> lwBookOutApplyDOS = bookOutApplyMapper.selectList(new LambdaQueryWrapperX<LwBookOutApplyDO>()
                .eq(LwBookOutApplyDO::getApplyUid, loginUserId)
                .ne(LwBookOutApplyDO::getProcessStatus, LwProcessEnum.REJECT.getProcess())
                .ge(LwBookOutApplyDO::getCreateTime, LocalDateTime.now().with(LocalTime.MIN))
                .le(LwBookOutApplyDO::getCreateTime, LocalDateTime.now()));
        if (lwBookOutApplyDOS.isEmpty()) {
            return 0L;
        }
        //lwBookOutApplyDOS的lwBookIds字段按逗号分隔，取数量
        return lwBookOutApplyDOS.stream().mapToLong(lwBookOutApplyDO ->
                Arrays.stream(lwBookOutApplyDO.getLwBookIds().split(",")).count()).sum();
    }

    @Override
    public CommonResult<List<LwBookInApplyRes>> myInApplies() {
        Long loginUserId = getLoginUserId();
        List<LwBookInApplyDO> lwBookInApplies = bookInApplyMapper.selectList(new LambdaQueryWrapperX<LwBookInApplyDO>()
                .eq(LwBookInApplyDO::getApplyUid, loginUserId)
                .orderByDesc(LwBookInApplyDO::getCreateTime)
        );
        if (lwBookInApplies.isEmpty()) {
            return CommonResult.success(new ArrayList<>());
        }
        List<LwBookInApplyRes> inApplyRes = BeanUtils.toBean(lwBookInApplies, LwBookInApplyRes.class);
        List<String> bookIds = new ArrayList<>();
        for (LwBookInApplyRes inApply : inApplyRes) {
            bookIds.addAll(Arrays.asList(inApply.getLwBookIds().split(",")));
            inApply.setLwBookIdList(Arrays.asList(inApply.getLwBookIds().split(",")));
        }
        List<LwBookDO> books = lwBookMapper.selectByIds(bookIds);
        for (LwBookInApplyRes inApplyRe : inApplyRes) {
            inApplyRe.setBookResList(books.stream()
                    .filter(book -> inApplyRe.getLwBookIdList().contains(book.getId().toString()))
                    .map(book -> BeanUtils.toBean(book, LwBookRespVO.class))
                    .collect(Collectors.toList()));
        }
        return CommonResult.success(inApplyRes);
    }

    @Override
    public CommonResult<List<LwBookOutApplyRes>> myOutApplies() {
        Long loginUserId = getLoginUserId();
        List<LwBookOutApplyDO> lwBookOutApplies = bookOutApplyMapper.selectList(new LambdaQueryWrapperX<LwBookOutApplyDO>()
                .eq(LwBookOutApplyDO::getApplyUid, loginUserId)
                .orderByDesc(LwBookOutApplyDO::getCreateTime)
        );
        if (lwBookOutApplies.isEmpty()) {
            return CommonResult.success(new ArrayList<>());
        }
        List<LwBookOutApplyRes> outApplyRes = BeanUtils.toBean(lwBookOutApplies, LwBookOutApplyRes.class);
        List<String> bookIds = new ArrayList<>();
        for (LwBookOutApplyRes outApply : outApplyRes) {
            bookIds.addAll(Arrays.asList(outApply.getLwBookIds().split(",")));
            outApply.setLwBookIdList(Arrays.asList(outApply.getLwBookIds().split(",")));
        }
        List<LwBookDO> books = lwBookMapper.selectByIds(bookIds);
        for (LwBookOutApplyRes outApplyRe : outApplyRes) {
            outApplyRe.setBookResList(books.stream()
                    .filter(book -> outApplyRe.getLwBookIdList().contains(book.getId().toString()))
                    .map(book -> BeanUtils.toBean(book, LwBookRespVO.class))
                    .collect(Collectors.toList()));
        }
        return CommonResult.success(outApplyRes);
    }

    @Override
    public CommonResult<BookInfoReq> isbnSearch(IsbnReq req) {
        if (!ISBNUtils.isISBN(req.getIsbn())) {
            throw new ServiceException("请输入正确的ISBN编码");
        }
        //如果在库，则使用在库书籍信息
        LwBookDO lwBookDO = lwBookMapper.selectOne(new LambdaQueryWrapperX<LwBookDO>()
                .eq(LwBookDO::getIsbn, req.getIsbn())
                .in(LwBookDO::getStoreStatus,
                        Arrays.asList(BookStoreEnum.IN_STORE.getProcess(), BookStoreEnum.OUT_STORE.getProcess()))
                .orderByDesc(LwBookDO::getUpdateTime)
                .limitOne()
        );
        if (lwBookDO != null) {
            return CommonResult.success(new BookInfoReq(lwBookDO));
        }
        IsbnApiVo bookInfo = ISBNUtils.getBookInfoByISBNApi(req.getIsbn());
        return CommonResult.success(new BookInfoReq(bookInfo));
    }

    @Override
    public CommonResult<BookInfoReq> isbnSearchByApi(IsbnReq req) {
        if (!ISBNUtils.isISBN(req.getIsbn())) {
            throw new ServiceException("请输入正确的ISBN编码");
        }
        IsbnApiVo bookInfo = ISBNUtils.getBookInfoByISBNApi(req.getIsbn());
        return CommonResult.success(new BookInfoReq(bookInfo));
    }

    @Override
    public CommonResult<List<NewBookRes>> newBooks() {
        List<LwBookDO> books = lwBookMapper.selectList(new LambdaQueryWrapperX<LwBookDO>()
                .eq(LwBookDO::getApplyStatus, LwProcessEnum.PASS.getProcess())
                .eq(LwBookDO::getStoreStatus, BookStoreEnum.IN_STORE.getProcess())
                .orderByDesc(LwBookDO::getStoreTime)
                .last("limit 4")
        );
        return CommonResult.success(BeanUtils.toBean(books, NewBookRes.class));
    }

    @Override
    public CommonResult<StatisDataRes> statisData(StatisDataReq req) {
        //时间条件查询。
        if (req.getCreateTime() == null) {
            req.setCreateTime(new LocalDateTime[2]);
            //设置为0点
            req.getCreateTime()[0] = LocalDateTime.now().minusDays(30).with(LocalTime.MIN);
            req.getCreateTime()[1] = LocalDateTime.now().with(LocalTime.MAX);
        }

        Long bookTotalCount = lwBookMapper.selectCount();
        Long userTotalCount = memberUserApi.userCount();
        Long applyTotalCount = bookInApplyMapper.selectCount() + bookOutApplyMapper.selectCount();
        Long lifeUserCount = memberUserApi.lifeUserCount();

        //req的时间范围内发起的漂入申请数量
        Long inBookUserCount = bookInApplyMapper.selectCount(new LambdaQueryWrapperX<LwBookInApplyDO>()
                .between(LwBookInApplyDO::getCreateTime, req.getCreateTime()[0], req.getCreateTime()[1])
        );

        //req的时间范围内发起的漂出申请数量
        Long outBookUserCount = bookOutApplyMapper.selectCount(new LambdaQueryWrapperX<LwBookOutApplyDO>()
                .between(LwBookOutApplyDO::getCreateTime, req.getCreateTime()[0], req.getCreateTime()[1])
        );

        //req的时间范围内漂入书籍数量
        Long inBookCount = lwBookMapper.selectCount(new LambdaQueryWrapperX<LwBookDO>()
                .between(LwBookDO::getStoreTime, req.getCreateTime()[0], req.getCreateTime()[1])
        );

        //req的时间范围内漂出书籍数量（这里假设漂出书籍也是通过storeTime字段记录）
        long outBookCount = 0L;
        List<LwBookOutApplyDO> lwBookOutApplies = bookOutApplyMapper.selectList(new LambdaQueryWrapperX<LwBookOutApplyDO>()
                .select(LwBookOutApplyDO::getLwBookIds)
                .between(LwBookOutApplyDO::getCreateTime, req.getCreateTime()[0], req.getCreateTime()[1])
                .eq(LwBookOutApplyDO::getProcessStatus, LwProcessEnum.PASS.getProcess())
        );
        for (LwBookOutApplyDO lwBookOutApplyDO : lwBookOutApplies) {
            outBookCount += lwBookOutApplyDO.getLwBookIds().split(",").length;
        }

        // 获取新用户趋势数据
        MemberUserApi.ChartData userChartData = memberUserApi.getNewUserChartData(
                req.getCreateTime()[0], req.getCreateTime()[1]);

        // 获取新书籍趋势数据
        MemberUserApi.ChartData bookChartData = getNewBookChartData(
                req.getCreateTime()[0], req.getCreateTime()[1]);

        // 构建返回结果
        StatisDataRes result = new StatisDataRes();
        result.setBookTotalCount(bookTotalCount);
        result.setUserTotalCount(userTotalCount);
        result.setApplyTotalCount(applyTotalCount);
        result.setLifeUserCount(lifeUserCount);
        result.setInBookUserCount(inBookUserCount);
        result.setOutBookUserCount(outBookUserCount);
        result.setInBookCount(inBookCount);
        result.setOutBookCount(outBookCount);
        result.setNewUserChart(userChartData);
        result.setNewBookChart(bookChartData);

        return CommonResult.success(result);
    }

    @Override
    public MemberUserApi.ChartData getNewBookChartData(LocalDateTime startTime, LocalDateTime endTime) {
        List<String> xAxisData = new ArrayList<>();
        List<Long> yAxisData = new ArrayList<>();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
        LocalDateTime currentDate = startTime.toLocalDate().atStartOfDay();
        while (!currentDate.isAfter(endTime)) {
            LocalDateTime dayStart = currentDate;
            LocalDateTime dayEnd = currentDate.with(LocalTime.MAX);
            Long count = lwBookMapper.countByStoreTimeBetween(dayStart, dayEnd);
            xAxisData.add(currentDate.format(formatter));
            yAxisData.add(count);
            currentDate = currentDate.plusDays(1);
        }
        return new MemberUserApi.ChartData(xAxisData, yAxisData);
    }


}

